最近上课讲了一个挺陌生的概念,叫做Skip List。搜索了一下,中文名称作“跳表”。
写这个题目的原因:
不过中文blog讲得都很浅:
http://hideto.javaeye.com/blog/297625
http://www.kernelchina.org/?q=node/239
(刚刚baidu搜索了一下,百度文库里面有很好的介绍Skip List的文章:
【1】http://wenku.baidu.com/view/7285945f804d2b160b4ec0a8.html —— 线段跳表 —— 跳表的一个拓展 by 石家庄二中 李骥扬
【2】http://wenku.baidu.com/view/d1bd5a0e7cd184254b3535af.html —— 让算法的效率“跳起来”!—— by 华东师范大学第二附属中学 魏冉
不过,都缺乏了对应的分析部分。)
英文站点还是有很多详细的解释的:
【3】http://courses.csail.mit.edu/6.046/spring04/handouts/skiplists.pdf
我来尝试写一下,自己对于Skip List的理解。希望能补全下不足的中文资料。
Skip List的原理:
(如果感觉这里对于Skip List说明的不完整,请移步参考资料【1】/【2】/亲自google,本文重点在于理论分析+证明)
产生动机:
我们知道,如果用有序数组进行二分查找(Binary Search),则用时为O(log n)。
但有序数组的问题是:
1. 它的容量有限,不能插入比它更多的元素;
2. 每次增加一个元素,需要O(n)的时间,这样花费很大
所以我们会选择用链表(Linked-List)来实现数据存储。但链表的问题是,因为只能进行linear search,查找耗时O(n)。
因此,我们希望一种存储方式,能够在链表上实现O(log n)的查找时间。
一个最初的想法:
每次查找的时候,因为元素是有序排列的,所以在进行查找的时候,比如查找数字7——即使跳过数字1~6,也不妨碍对7的发现。所以,在查找元素的时候,如果能够看到几个元素之后的值,就能够决定是否要跳过这些元素。从而缩短了查找的时间。
就好像火车,有快车有慢车;快车停得站少,慢车停得多。所以,从一个地方到另一个地方,我们需要先乘坐快车,之后换乘慢车。
假设我们建立的一层快车,每两个元素直接跳过b个元素,那么每次查找的时间消耗(worst-case):T1 = n/b + b。
易知,当n/b = b时,T1最小。此时b = sqrt(n),T1 = 2sqrt(n) = O(sqrt(n))。
假设我们建立两层快车,第二层比第一层多跳过b个元素,那么每次查找时间(worst-case):
T2 = n/b + n/b2 + b;(先放在这里,之后会统一求)
现在假设我们建立了m层快车,每层的两个元素之间跳过上一层的b个元素;那么每次查找时间(worst-case):
(在平板电脑上手写的,图片加载时间可能会比较长)
因此,合适的Skip-List情形,是每次跳过2个元素(b),一共log(n)层。
但是呢~如果依照这个想法建立SkipList,需要记录每次的高度;而且在插入新元素的时候,需要更新所有的层的所有相关节点……这样的任务量是非常大的!
怎么办呢?
神奇的地方
于是……神奇的地方出现了~我们让概率来决定——每次增加一个新的元素,我们就扔一枚硬币,看看是不是要向上增长一层~之后从概率上可以证明,这样能保持Skip List的性质~
Skip List的性质
在进行下一阶段之前,这是对于Skip List结构性质的总结(摘自【2】):
- 跳跃表由多条链构成(S0,S1,S2 ……,Sh),且满足如下三个条件:
- 每条链必须包含两个特殊元素:+∞ 和 -∞
- S0包含所有的元素,并且所有链中的元素按照升序排列。
- 每条链中的元素集合必须包含于序数较小的链的元素集合
(图片来自【2】)
Skip List的时空效率:
(摘自【2】)
- 空间复杂度: O(n) (期望)
- 跳跃表高度: O(log n) (期望)
ü 相关操作的时间复杂度:
- 查找: O(log n) (期望)
- 插入: O(log n) (期望)
- 删除: O(log n) (期望)
关于时空效率的证明:
1. 空间复杂度 O(n):
对于每层的期待:第一层n,第二层n/2,第三层n/22,...,直到 n/2log n=1。所以,总空间需求:
S = n + n/2 + n/22 + ... + n/2log n < n(1 + 1/2 + 1/22 + ... + 1/2∞) =2n
因此他的空间复杂度为 2n = O(n)
2. Skip List高度:
对每层来说,它会向上增长的概率为1/2,则第m层向上增长的概率为1/2m;n个元素,则在m层元素数目的期待为Em = n/2m;当Em = 1,m = log2n即为层数的期待。故其高度期待为 Eh = O(log n)。
*关于“高概率(high probability)”的定义:
(参考【3】【4】)
对于事件A,如果它发生的概率至少为1-cα/nα,其中cα仅取决于α,那么我们称它为高概率。
在牺牲时间和/或空间的情况下,我们可以选择α的值。
(3~5条目证明起来很困难……先放在这里吧)
3. 查找的复杂度:
claim:在高概率的前提下,查找的时间复杂度为O(log n)
4. 插入的复杂度:
claim:在高概率的前提下,插入的时间复杂度为O(log n)
首先通过查找找到要插入的位置:O(log n)
之后进行插入,同时对每层进行对应的更新(依照概率决定是否向上增长):o(log n)
3. 删除的复杂度:
claim:在高概率的前提下,删除的时间复杂度为O(log n)
References:
【1】 线段跳表 —— 跳表的一个拓展 by 石家庄二中 李骥扬, taken on 2011年2月23日,from:http://wenku.baidu.com/view/7285945f804d2b160b4ec0a8.html
【2】 让算法的效率“跳起来”!—— by 华东师范大学第二附属中学 魏冉, taken on 2011年2月23日,from:http://wenku.baidu.com/view/d1bd5a0e7cd184254b3535af.html